<?php

declare(ENCODING = 'UTF-8');

/*                                                                        *
 * This script is part of the TYPO3 project - inspiring people to share!  *
 *                                                                        *
 * TYPO3 is free software; you can redistribute it and/or modify it under *
 * the terms of the GNU General Public License version 2 as published by  *
 * the Free Software Foundation.                                          *
 *                                                                        *
 * This script is distributed in the hope that it will be useful, but     *
 * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHAN-    *	
 * TABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General      *
 * Public License for more details.                                       *
 *                                                                        */	

/**
 * @package auxo
 * @subpackage AOP
 * @version $Id$
 */

/**	
 * The AOP Component Proxy Builder
 *	
 * @package auxo
 * @subpackage AOP
 * @version $Id$	
 * @copyright Copyright belongs to the respective authors
 * @author andreas.horn@extronaut.de
 * @license http://opensource.org/licenses/gpl-license.php GNU Public License, version 2
 */
class tx_auxo_AOPComponentProxyBuilder {
	
	/**
	 * Builds a AOP proxy class using class inheritance
	 *
	 * @param string $classname
	 * @param string $proxyname
	 * @param array $advices
	 * @param array $introductions
	 * @return string $coding
	 */
	static public function build($classname, $proxyname, array $introductions=NULL, array $advices=NULL) {
		$inspector = new tx_auxo_ClassInspector();
		$reflection = $inspector->getReflection($classname);
		
		foreach($reflection->methods as $method) {
			if ($method->isFinal() || $method->isAbstract()) {
				continue;
			}
			
			if (isset($advises[$method->getName()])) {
				$proxyMethods[] = self::createProxyMethod($method, $advices[$method->getName()]);
			}				
			else {
				$proxyMethods[] = self::createProxyMethod($method);
			}			
		}
		
		return self::createProxyClassHeader() . self::createProxyClass($proxyname, $classname, $proxyMethods);
	}
	
	/**
	 * Returns a header for a proxy class
	 *
	 * @return $coding
	 */
	static protected function createProxyClassHeader() {
		return sprintf('/* proxy class generated at %s */', date('d-m-y h:s:m', time()));
	}
	
	/**
	 * Creates coding of a proxy class
	 *
	 * @param string $classname
	 * @param string $parent
	 * @param array $methods
	 * @return string $coding
	 */
	static protected function createProxyClass($classname, $parent, $methods) {
		return sprintf('class %s extends %s { %s }', $classname, $parent, implode(' ', $methods));
	}
	
	/**
	 * Creates coding of a method for a proxy class
	 *
	 * @param ReflectionMethod $method
	 * @param array $advises
	 * @return string $coding
	 */
	static protected function createProxyMethod($method, $advises=array()) {
		$names = array();
		foreach($method->getParameters() as $parameter) {
			$names[] = '$' . $parameter->getName();
		}
		
		$invoke = 'parent::' . $method->getName() . '(' . implode(',', $names) . ')';
		
		if (! isset($advises['afterThrowing'])) {
			$advises['afterThrowing'] = 'throw $e;';
		}
		
		$body = sprintf('{
							%s;
		                    try {
		                    	$returned = %s;
		                    	%s
		                    }
		                    catch(Exception $e) {
		                    	%s
		                    }
		                    %s
		                    return $returned;
						 }', 
					isset($advises['before']) ? $advises['before'] : '',
					$invoke,
					isset($advises['after']) ? $advises['after'] : '',
					isset($advises['afterThrowing']) ? $advises['afterThrowing'] : '',
					isset($advises['afterReturning']) ? $advises['afterReturning'] : ''
				);
							
		return self::getMethodSignature($method). $body;							        
	}
	
	/**
	 * Creates a method signature
	 *
	 * @param ReflectionMethod $method
	 * @return string $coding
	 */
	static protected function getMethodSignature($method) {	
		foreach (Reflection::getModifierNames($method->getModifiers()) as $modifier) {
			$signature.= $modifier . ' ';
		}		
		
		$parameters = $method->getParameters();
		$arguments = array();
		
		foreach($parameters as $parameter) {
			$arguments[] = sprintf('%s%s%s%s',
			                       self::getParameterTypeHint($parameter),
			                       $parameter->isPassedByReference() ? '&$' : '$',
			                       $parameter->getName(),
			                       self::getParameterDefaultValue($parameter)
			               );
		}

		return $signature . 'function ' . $method->getName() . '(' . implode(',', $arguments) . ')';
	}
	
	/**
	 * Creates a parameter type hint for a method signature
	 *
	 * @param ReflectionParameter $parameter
	 * @return string $typeHint
	 */
	static protected function getParameterTypeHint($parameter) {
		if ($parameter->isArray()) {
			return 'array ';
		}
		elseif ($parameter->getClass() instanceof ReflectionClass) {
			return $parameter->getClass()->getName() . ' ';
		}
		else {
			return '';
		}	
	}
	
	/**
	 * Creates a parameter default value for a method signature
	 *
	 * @param RelfectionParameter $parameter
	 * @return string $defaultValue
	 */
	static protected function getParameterDefaultValue($parameter) {
		if (! $parameter->isOptional() || ! $parameter->isDefaultValueAvailable()) {
			return '';
		}
		
		/*
		 * Getting the default value of a method parameter is a bit tricky because reflection 
		 * does not supported defaults like $a=false. Therefore I used __tostring and extract
		 * it manually. Parameters are formated as followed:
		 * [ <optional|required> $parameter = value ]
		 */
		$parameterAsString = sprintf('%s', $parameter);
		
		if (($position = strpos($parameterAsString, '='))) {
			return str_replace(' ', '', substr($parameterAsString, $position, strlen($parameterAsString) - $position - 1));
		}
		
		return '';
	}
}
?>